package com.yet.config;

import java.lang.annotation.Annotation;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.beans.factory.config.BeanDefinitionHolder;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.DependencyDescriptor;
import org.springframework.beans.factory.support.AutowireCandidateResolver;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.DefaultListableBeanFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationEvent;
import org.springframework.context.ApplicationListener;
import org.springframework.context.annotation.AnnotationConfigUtils;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.context.event.ContextRefreshedEvent;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.type.AnnotationMetadata;

import com.yet.annotation.EnableRpc;
import com.yet.annotation.RpcAutowrie;
import com.yet.annotation.RpcService;
import com.yet.container.Container;
import com.yet.container.registry.LocalServiceStateHolder;
import com.yet.container.registry.RegistryMeta;
import com.yet.container.registry.RegistryMeta.Address;
import com.yet.container.registry.RegistryMeta.ServiceMeta;
import com.yet.container.remote.Remoting;
import com.yet.container.remote.netty.client.Client;
import com.yet.container.remote.netty.server.Server;
import com.yet.exception.ServiceException;
import com.yet.listener.LocalRefreshEvent;
import com.yet.proxy.ClientInvocationHandler;
import com.yet.proxy.YetProxy;
import com.yet.serialization.Serialization;
import com.yet.utils.BeanNameUtils;

/***
 * Bean声明
 */
public class RpcBeanDefinitionRegistrar
		implements ImportBeanDefinitionRegistrar, BeanFactoryPostProcessor, ApplicationListener<ApplicationEvent> {

	private static final Logger logger = LoggerFactory.getLogger(RpcBeanDefinitionRegistrar.class);

	private DefaultListableBeanFactory beanFactory;

	private List<Container> list = new ArrayList<>();

	private LocalServiceStateHolder localServiceStateHolder;

	public static final String YETPROXY_BEANNAME = YetProxy.class.getName();

	public static final String SERIALIZATION_BEANNAME = Serialization.class.getName();

	public static final String RPC_BEANDEFINITION_REGISTRAR_BEANNAME = RpcBeanDefinitionRegistrar.class.getName();

	private Map<String, Object> defaultAttrs = null;

	@Override
	public void registerBeanDefinitions(AnnotationMetadata metadata, BeanDefinitionRegistry registry) {
		try {
			registerDefaultConfiguration(metadata, registry);
		} catch (ClassNotFoundException e) {
			throw new ServiceException(e);
		}
	}

	/***
	 * 注册serialization 和 remoting
	 * 
	 * @param metadata
	 * @param registry
	 * @throws ClassNotFoundException
	 */
	private void registerDefaultConfiguration(AnnotationMetadata metadata, BeanDefinitionRegistry registry)
			throws ClassNotFoundException {

		defaultAttrs = metadata.getAnnotationAttributes(EnableRpc.class.getName(), true);

		if (defaultAttrs != null) {
			// Introspector.decapitalize(serializationClass.getSimpleName())
			Class<?> serializationClass = Class.forName((String) defaultAttrs.get("serializationClass"));
			registry.registerBeanDefinition(SERIALIZATION_BEANNAME,
					BeanDefinitionBuilder.genericBeanDefinition(serializationClass).getBeanDefinition());

			Class<?> yetProxyClass = Class.forName((String) defaultAttrs.get("yetProxyClass"));
			registry.registerBeanDefinition(YETPROXY_BEANNAME,
					BeanDefinitionBuilder.genericBeanDefinition(yetProxyClass).getBeanDefinition());
		}

		registry.registerBeanDefinition(RPC_BEANDEFINITION_REGISTRAR_BEANNAME,
				BeanDefinitionBuilder.genericBeanDefinition(RpcBeanDefinitionRegistrar.class).getBeanDefinition());
	}

	@Override
	public void postProcessBeanFactory(ConfigurableListableBeanFactory configurableListableBeanFactory)
			throws BeansException {
		this.beanFactory = (DefaultListableBeanFactory) configurableListableBeanFactory;
		loadDefaultConfiguration();
		setAutowiredAnnotationType();
	}

	/**
	 * 加载默认配置到当前类
	 */
	private void loadDefaultConfiguration() {
		this.localServiceStateHolder = beanFactory.getBean(LocalServiceStateHolder.class);
	}

	/**
	 * 添加自定义的注入注解
	 */
	private void setAutowiredAnnotationType() {
		AutowiredAnnotationBeanPostProcessor autowiredAnnotationBean = (AutowiredAnnotationBeanPostProcessor) beanFactory
				.getBean(AnnotationConfigUtils.AUTOWIRED_ANNOTATION_PROCESSOR_BEAN_NAME);
		autowiredAnnotationBean.setAutowiredAnnotationTypes(
				Stream.of(RpcAutowrie.class, Autowired.class, Value.class).collect(Collectors.toSet()));
		AutowireCandidateResolver autowireCandidateResolver = beanFactory.getAutowireCandidateResolver();
		beanFactory.setAutowireCandidateResolver(new RpcAutowireCandidateResolver(autowireCandidateResolver));
	}

	class RpcAutowireCandidateResolver implements AutowireCandidateResolver {

		private final AutowireCandidateResolver autowireCandidateResolver;
		private final YetProxy proxy;

		public RpcAutowireCandidateResolver(AutowireCandidateResolver autowireCandidateResolver) {
			this.autowireCandidateResolver = autowireCandidateResolver;
			proxy = beanFactory.getBean(YETPROXY_BEANNAME, YetProxy.class);
		}

		@Override
		public boolean isAutowireCandidate(BeanDefinitionHolder bdHolder, DependencyDescriptor descriptor) {
			return autowireCandidateResolver.isAutowireCandidate(bdHolder, descriptor);
		}

		/***
		 * 这里首先声明一个bean,然后返回建议值
		 */
		@Override
		public Object getSuggestedValue(DependencyDescriptor descriptor) {
			Annotation[] annotations = descriptor.getAnnotations();
			for (Annotation ann : annotations) {
				if (ann.annotationType() == RpcAutowrie.class) {
					RpcAutowrie auto = (RpcAutowrie) ann;
					String serviceName = auto.serviceName();
					Class<? extends Remoting> remotingClass = auto.remotingClass();
					if (!beanFactory.containsBean(serviceName)) {
						Client client = beanFactory.getBean(remotingClass).getClient(new ServiceMeta(auto));
						Object proxyObj = proxy.proxy(descriptor.getField().getType(),
								new ClientInvocationHandler(auto, client));
						beanFactory.registerSingleton(serviceName, proxyObj);
						return proxyObj;
					}
					return beanFactory.getBean(serviceName);
				}
			}
			return autowireCandidateResolver.getSuggestedValue(descriptor);
		}

		@Override
		public Object getLazyResolutionProxyIfNecessary(DependencyDescriptor descriptor, String beanName) {
			return autowireCandidateResolver.getLazyResolutionProxyIfNecessary(descriptor, beanName);
		}
	}

	@Override
	public void onApplicationEvent(ApplicationEvent event) {
		/** 初始化 */
		if (event instanceof ContextRefreshedEvent) {
			ApplicationContext context = ((ContextRefreshedEvent) event).getApplicationContext();
			startContainers(context);
			registryAndStarter(context);// 注册服务并启动监听
		} else if (event instanceof LocalRefreshEvent) {
			logger.debug("开始刷新本地服务");
			refresh(((ContextRefreshedEvent) event).getApplicationContext());
		}

	}

	/**
	 * 启动容器
	 */
	public void startContainers(ListableBeanFactory beanFactory) {
		Map<String, Container> containers = beanFactory.getBeansOfType(Container.class);
		if (containers != null) {
			containers.values().forEach(c -> c.start());
			list.addAll(containers.values());
		}
	}

	/**
	 * 注册并开启服务
	 * 
	 * @param applicationContext
	 */
	public void registryAndStarter(ApplicationContext applicationContext) {
		String[] serverNames = applicationContext.getBeanNamesForAnnotation(com.yet.annotation.RpcService.class);
		Set<Server> servers = new HashSet<>();
		for (String serverName : serverNames) {
			Object bean = applicationContext.getBean(serverName);
			RpcService rpcService = AnnotationUtils.findAnnotation(bean.getClass(), RpcService.class);
			registryAlias(serverName, BeanNameUtils.getName(rpcService));
			Address address = new Address(rpcService.host(), rpcService.port());
			ServiceMeta serviceMeta = new ServiceMeta(rpcService);
			RegistryMeta registryMeta = new RegistryMeta(address, serviceMeta);
			// 打开remoting传输
			Server server = beanFactory.getBean(rpcService.remotingClass()).getServer(registryMeta);
			servers.add(server);
			// 注册中心
			localServiceStateHolder.registry(registryMeta, bean);
		}
		servers.forEach(s -> s.start());
		list.sort(null);
		stopContainersWithShutdownHook(list);
	}

	private void registryAlias(String beanName, String alias) {
		String[] aliases = beanFactory.getAliases(beanName);
		if (!ArrayUtils.contains(aliases, alias)) {
			beanFactory.registerAlias(beanName, alias);
		}
	}

	/**
	 * 取消注册的所有的服务 重新注册所有服务
	 * 
	 * @param applicationContext
	 */
	public synchronized void refresh(ApplicationContext applicationContext) {
		// 关闭启动的服务
		logger.debug("1> 停止启动的服务");
		stopContainersNow(list);
		logger.debug("2> 注册中心注销服务");
		localServiceStateHolder.cancelAllRegistryService();
		logger.debug("3> 本地注销服务");
		localServiceStateHolder.clear();
		// 重新加载服务
		list.clear();
		logger.debug("4> 重新加载服务");
		registryAndStarter(applicationContext);
	}

	private void stopContainersWithShutdownHook(Collection<Container> containers) {
		containers.forEach(c -> logger.debug(c.getState().toString()));
		Runtime.getRuntime().addShutdownHook(new Thread(() -> containers.forEach(c -> c.stop())));
	}

	private void stopContainersNow(Collection<Container> containers) {
		containers.forEach(c -> c.stop());
	}
}
